<!DOCTYPE html>
<html>
<head>
    

    

    



    <meta charset="utf-8">
    
    
    
    <title>Android架构组件(七)-Room | 随手记 | 不积跬步无以至千里</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    
    <meta name="theme-color" content="#1c9d5c">
    
    
    <meta name="keywords" content="Android,Architecture Components">
    <meta name="description" content="利用Room保存数据到本地数据库 Room提供了一个SQLite的抽象层，可以在充分利用SQLite的同时访问流畅的数据库。 数据的本地持久化对某些需要处理大量结构化数据的App非常有益。最常见的用例是缓存相关的数据。这样，当设备无法访问网络时，用户仍然可以在脱机状态下浏览该内容。设备重新联机后，任何用户造成的内容更改都会同步到服务器。 由于Room会为您处理这些问题，我们强烈建议使用Room而不">
<meta name="keywords" content="Android,Architecture Components">
<meta property="og:type" content="article">
<meta property="og:title" content="Android架构组件(七)-Room">
<meta property="og:url" content="http://yoursite.com/2017/10/11/architecture-room/index.html">
<meta property="og:site_name" content="随手记">
<meta property="og:description" content="利用Room保存数据到本地数据库 Room提供了一个SQLite的抽象层，可以在充分利用SQLite的同时访问流畅的数据库。 数据的本地持久化对某些需要处理大量结构化数据的App非常有益。最常见的用例是缓存相关的数据。这样，当设备无法访问网络时，用户仍然可以在脱机状态下浏览该内容。设备重新联机后，任何用户造成的内容更改都会同步到服务器。 由于Room会为您处理这些问题，我们强烈建议使用Room而不">
<meta property="og:locale" content="zh-CN">
<meta property="og:image" content="http://yoursite.com/2017/10/11/architecture-room/room_architecture.png">
<meta property="og:updated_time" content="2018-04-15T04:52:29.497Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Android架构组件(七)-Room">
<meta name="twitter:description" content="利用Room保存数据到本地数据库 Room提供了一个SQLite的抽象层，可以在充分利用SQLite的同时访问流畅的数据库。 数据的本地持久化对某些需要处理大量结构化数据的App非常有益。最常见的用例是缓存相关的数据。这样，当设备无法访问网络时，用户仍然可以在脱机状态下浏览该内容。设备重新联机后，任何用户造成的内容更改都会同步到服务器。 由于Room会为您处理这些问题，我们强烈建议使用Room而不">
<meta name="twitter:image" content="http://yoursite.com/2017/10/11/architecture-room/room_architecture.png">
    
        <link rel="alternate" type="application/atom+xml" title="随手记" href="/atom.xml">
    
    <link rel="shortcut icon" href="/favicon.ico">
    <link rel="stylesheet" href="/css/style.css?v=1.7.2">
    <script>window.lazyScripts=[]</script>

    <!-- custom head -->
    

</head>

<body>
    <div id="loading" class="active"></div>

    <aside id="menu" class="hide" >
  <div class="inner flex-row-vertical">
    <a href="javascript:;" class="header-icon waves-effect waves-circle waves-light" id="menu-off">
        <i class="icon icon-lg icon-close"></i>
    </a>
    <div class="brand-wrap" style="background-image:url(/img/brand.jpg)">
      <div class="brand">
        <a href="/" class="avatar waves-effect waves-circle waves-light">
          <img src="/img/avatar.jpg">
        </a>
        <hgroup class="introduce">
          <h5 class="nickname">马建</h5>
          <a href="mailto:markjoker@126.com" title="markjoker@126.com" class="mail">markjoker@126.com</a>
        </hgroup>
      </div>
    </div>
    <div class="scroll-wrap flex-col">
      <ul class="nav">
        
            <li class="waves-block waves-effect">
              <a href="/"  >
                <i class="icon icon-lg icon-home"></i>
                主页
              </a>
            </li>
        
            <li class="waves-block waves-effect">
              <a href="/archives"  >
                <i class="icon icon-lg icon-archives"></i>
                归档
              </a>
            </li>
        
            <li class="waves-block waves-effect">
              <a href="/tags"  >
                <i class="icon icon-lg icon-tags"></i>
                标签
              </a>
            </li>
        
            <li class="waves-block waves-effect">
              <a href="/categories"  >
                <i class="icon icon-lg icon-th-list"></i>
                分类
              </a>
            </li>
        
            <li class="waves-block waves-effect">
              <a href="https://github.com/markjoker" target="_blank" >
                <i class="icon icon-lg icon-github"></i>
                Github
              </a>
            </li>
        
            <li class="waves-block waves-effect">
              <a href="http://www.weibo.com/markjoker" target="_blank" >
                <i class="icon icon-lg icon-weibo"></i>
                微博
              </a>
            </li>
        
            <li class="waves-block waves-effect">
              <a href="/cv"  >
                <i class="icon icon-lg icon-file"></i>
                简历
              </a>
            </li>
        
      </ul>
    </div>
  </div>
</aside>

    <main id="main">
        <header class="top-header" id="header">
    <div class="flex-row">
        <a href="javascript:;" class="header-icon waves-effect waves-circle waves-light on" id="menu-toggle">
          <i class="icon icon-lg icon-navicon"></i>
        </a>
        <div class="flex-col header-title ellipsis">Android架构组件(七)-Room</div>
        
        <div class="search-wrap" id="search-wrap">
            <a href="javascript:;" class="header-icon waves-effect waves-circle waves-light" id="back">
                <i class="icon icon-lg icon-chevron-left"></i>
            </a>
            <input type="text" id="key" class="search-input" autocomplete="off" placeholder="输入感兴趣的关键字">
            <a href="javascript:;" class="header-icon waves-effect waves-circle waves-light" id="search">
                <i class="icon icon-lg icon-search"></i>
            </a>
        </div>
        
        
        <a href="javascript:;" class="header-icon waves-effect waves-circle waves-light" id="menuShare">
            <i class="icon icon-lg icon-share-alt"></i>
        </a>
        
    </div>
</header>
<header class="content-header post-header">

    <div class="container fade-scale">
        <h1 class="title">Android架构组件(七)-Room</h1>
        <h5 class="subtitle">
            
                <time datetime="2017-10-11T07:41:10.000Z" itemprop="datePublished" class="page-time">
  2017-10-11
</time>


            
        </h5>
    </div>

    


</header>


<div class="container body-wrap">
    
    <aside class="post-widget">
        <nav class="post-toc-wrap post-toc-shrink" id="post-toc">
            <h4>目录</h4>
            <ol class="post-toc"><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#利用Room保存数据到本地数据库"><span class="post-toc-number">1.</span> <span class="post-toc-text">利用Room保存数据到本地数据库</span></a></li><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#使用Room实体定义数据"><span class="post-toc-number">2.</span> <span class="post-toc-text">使用Room实体定义数据</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#使用主键"><span class="post-toc-number">2.1.</span> <span class="post-toc-text">使用主键</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#注释索引和唯一性"><span class="post-toc-number">2.2.</span> <span class="post-toc-text">注释索引和唯一性</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#定义对象之间的关系"><span class="post-toc-number">2.3.</span> <span class="post-toc-text">定义对象之间的关系</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#创建嵌套对象"><span class="post-toc-number">2.4.</span> <span class="post-toc-text">创建嵌套对象</span></a></li></ol></li><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#使用Room-DAO访问数据"><span class="post-toc-number">3.</span> <span class="post-toc-text">使用Room DAO访问数据</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#定义便捷方法"><span class="post-toc-number">3.1.</span> <span class="post-toc-text">定义便捷方法</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#Insert"><span class="post-toc-number">3.1.1.</span> <span class="post-toc-text">Insert</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#Update"><span class="post-toc-number">3.1.2.</span> <span class="post-toc-text">Update</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#Delete"><span class="post-toc-number">3.1.3.</span> <span class="post-toc-text">Delete</span></a></li></ol></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#查询信息"><span class="post-toc-number">3.2.</span> <span class="post-toc-text">查询信息</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#简单查询"><span class="post-toc-number">3.2.1.</span> <span class="post-toc-text">简单查询</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#将参数传递给查询"><span class="post-toc-number">3.2.2.</span> <span class="post-toc-text">将参数传递给查询</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#返回列的子集"><span class="post-toc-number">3.2.3.</span> <span class="post-toc-text">返回列的子集</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#传递一组参数"><span class="post-toc-number">3.2.4.</span> <span class="post-toc-text">传递一组参数</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#可观察的查询"><span class="post-toc-number">3.2.5.</span> <span class="post-toc-text">可观察的查询</span></a></li></ol></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#使用RxJava进行反应查询"><span class="post-toc-number">3.3.</span> <span class="post-toc-text">使用RxJava进行反应查询</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#直接游标访问"><span class="post-toc-number">3.3.1.</span> <span class="post-toc-text">直接游标访问</span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#查询多个表"><span class="post-toc-number">3.3.2.</span> <span class="post-toc-text">查询多个表</span></a></li></ol></li></ol></li><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#迁移Room数据库"><span class="post-toc-number">4.</span> <span class="post-toc-text">迁移Room数据库</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#测试迁移"><span class="post-toc-number">4.1.</span> <span class="post-toc-text">测试迁移</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#导出模式"><span class="post-toc-number">4.2.</span> <span class="post-toc-text">导出模式</span></a></li></ol></li><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#测试你的数据库"><span class="post-toc-number">5.</span> <span class="post-toc-text">测试你的数据库</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#在Android设备上测试"><span class="post-toc-number">5.1.</span> <span class="post-toc-text">在Android设备上测试</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#在你的主机上测试"><span class="post-toc-number">5.2.</span> <span class="post-toc-text">在你的主机上测试</span></a></li></ol></li><li class="post-toc-item post-toc-level-1"><a class="post-toc-link" href="#使用Room引用复杂数据"><span class="post-toc-number">6.</span> <span class="post-toc-text">使用Room引用复杂数据</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#使用类型转换器"><span class="post-toc-number">6.1.</span> <span class="post-toc-text">使用类型转换器</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#了解Room不允许使用对象引用的原因"><span class="post-toc-number">6.2.</span> <span class="post-toc-text">了解Room不允许使用对象引用的原因</span></a></li></ol></li></ol>
        </nav>
    </aside>


<article id="post-architecture-room"
  class="post-article article-type-post fade" itemprop="blogPost">

    <div class="post-card">
        <h1 class="post-card-title">Android架构组件(七)-Room</h1>
        <div class="post-meta">
            <time class="post-time" title="2017-10-11 15:41:10" datetime="2017-10-11T07:41:10.000Z"  itemprop="datePublished">2017-10-11</time>

            


            
<span id="busuanzi_container_page_pv" title="文章总阅读量" style='display:none'>
    <i class="icon icon-eye icon-pr"></i><span id="busuanzi_value_page_pv"></span>
</span>


        </div>
        <div class="post-content" id="post-content" itemprop="postContent">
            <h1 id="利用Room保存数据到本地数据库"><a href="#利用Room保存数据到本地数据库" class="headerlink" title="利用Room保存数据到本地数据库"></a>利用Room保存数据到本地数据库</h1><hr>
<p>Room提供了一个SQLite的抽象层，可以在充分利用SQLite的同时访问流畅的数据库。</p>
<p>数据的本地持久化对某些需要处理大量结构化数据的App非常有益。最常见的用例是缓存相关的数据。这样，当设备无法访问网络时，用户仍然可以在脱机状态下浏览该内容。设备重新联机后，任何用户造成的内容更改都会同步到服务器。</p>
<p>由于Room会为您处理这些问题，我们强烈建议使用Room而不是SQLite。但是，如果您更愿意直接使用SQLite API，请阅读使用SQLite保存数据。</p>
<blockquote>
<p><strong>注意：</strong>为了在您的应用中使用Room，请将架构组件依赖添加到您应用的build.gradle文件中。</p>
</blockquote>
<p>Room由三个非常重要的部分组成：</p>
<ul>
<li><strong>Database:</strong>包含数据库持有者，并作为与应用持久关联数据的底层连接的主要访问点。用@Database注解的类应该满足以下条件：<ul>
<li>一个继承RoomDatabase的抽象类。</li>
<li>在注释中包含与数据库关联的实体列表。</li>
<li>包含一个无参的抽象方法，并返回用@Dao注解的类。<br>在运行时，您可以通过调用Room.databaseBuilder（）或Room.inMemoryDatabaseBuilder（）来获取数据库实例。</li>
</ul>
</li>
<li><strong>Entity:</strong>代表数据库中的表。</li>
<li><strong>DAO:</strong>包含用于访问数据库的方法。</li>
</ul>
<p>这些组件以及它们与应用程序其余部分的关系如图1所示：<br><figure class="image-bubble">
                <div class="img-lightbox">
                    <div class="overlay"></div>
                    <img src="/2017/10/11/architecture-room/room_architecture.png" alt="t1" title="">
                </div>
                <div class="image-caption">t1</div>
            </figure></p>
<font style="font-size:14px">图1. Room结构图</font>

<p>以下代码片段包含具有一个实体和一个DAO的示例数据库配置：<br><strong>User.java</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    <span class="meta">@PrimaryKey</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> uid;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"first_name"</span>)</span><br><span class="line">    <span class="keyword">private</span> String firstName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"last_name"</span>)</span><br><span class="line">    <span class="keyword">private</span> String lastName;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Getters and setters are ignored for brevity,</span></span><br><span class="line">    <span class="comment">// but they're required for Room to work.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>UserDao.java</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user"</span>)</span><br><span class="line">    <span class="function">List&lt;User&gt; <span class="title">getAll</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user WHERE uid IN (:userIds)"</span>)</span><br><span class="line">    <span class="function">List&lt;User&gt; <span class="title">loadAllByIds</span><span class="params">(<span class="keyword">int</span>[] userIds)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user WHERE first_name LIKE :first AND "</span></span><br><span class="line">           + <span class="string">"last_name LIKE :last LIMIT 1"</span>)</span><br><span class="line">    <span class="function">User <span class="title">findByName</span><span class="params">(String first, String last)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Insert</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">insertAll</span><span class="params">(User... users)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Delete</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">delete</span><span class="params">(User user)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>AppDatabase.java</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Database</span>(entities = &#123;User.class&#125;, version = <span class="number">1</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AppDatabase</span> <span class="keyword">extends</span> <span class="title">RoomDatabase</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> UserDao <span class="title">userDao</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在创建上述文件后，使用以下代码获取创建的数据库实例:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">AppDatabase db = Room.databaseBuilder(getApplicationContext(),</span><br><span class="line">        AppDatabase.class, <span class="string">"database-name"</span>).build();</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p><strong>注意：</strong>实例化AppDatabase对象时应遵循单例设计模式，因为每个RoomDatabase实例相当昂贵，并且很少需要访问多个实例。</p>
</blockquote>
<h1 id="使用Room实体定义数据"><a href="#使用Room实体定义数据" class="headerlink" title="使用Room实体定义数据"></a>使用Room实体定义数据</h1><hr>
<p>使用Room持久性库时，可以将相关字段集定义为实体。对于每个实体，在关联的数据库对象内创建一个表来保存这些项目。</p>
<p>默认情况下，Room会为实体中定义的每个字段创建一个列。 如果实体具有不想保留的字段，则可以使用@Ignore对其进行注释。 您必须通过Database类中的entities数组引用实体类。</p>
<p>以下代码片段显示了如何定义一个实体：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    <span class="meta">@PrimaryKey</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> id;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String firstName;</span><br><span class="line">    <span class="keyword">public</span> String lastName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Ignore</span></span><br><span class="line">    Bitmap picture;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>To persist a field, Room must have access to it. You can make a field public, or you can provide a getter and setter for it. If you use getter and setter methods, keep in mind that they’re based on JavaBeans conventions in Room.<br>为了持久化一个字段，Room必须有权访问它。你可以申明字段为public或者为字段提供一个getter/setter方法。如果你使用getter/setter方法，请记住它们基于Room中的JavaBeans约定。</p>
<blockquote>
<p><strong>注意：</strong>实体可以有一个空的构造函数（如果相应的DAO类可以访问每个持久化字段）或者一个构造函数的参数包含与实体中字段类型和名称相匹配的类型和名称。Room也可以使用全部或部分构造函数，例如仅接收一些字段的构造函数。</p>
</blockquote>
<h2 id="使用主键"><a href="#使用主键" class="headerlink" title="使用主键"></a>使用主键</h2><hr>
<p>每个实体必须至少定义1个字段作为主键。 即使只有1个字段，仍然需要使用@PrimaryKey注释对字段进行注释。 此外，如果您希望Room将自动ID分配给实体，则可以设置@PrimaryKey的autoGenerate属性。 如果实体具有复合主键，则可以使用@Entity批注的primaryKeys属性，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span>(primaryKeys = &#123;<span class="string">"firstName"</span>, <span class="string">"lastName"</span>&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> String firstName;</span><br><span class="line">    <span class="keyword">public</span> String lastName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Ignore</span></span><br><span class="line">    Bitmap picture;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>默认情况下，Room使用类名作为数据库表名。如果您希望表具有不同的名称，请设置@Entity批注的tableName属性，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span>(tableName = <span class="string">"users"</span>)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p><strong>警告：</strong>SQLite中的表名是不区分大小写的。</p>
</blockquote>
<p>与tableName属性类似，Room使用字段名称作为数据库中的列名称。如果您希望列的名称不同，请将@ColumnInfo注释添加到字段中，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span>(tableName = <span class="string">"users"</span>)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    <span class="meta">@PrimaryKey</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"first_name"</span>)</span><br><span class="line">    <span class="keyword">public</span> String firstName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"last_name"</span>)</span><br><span class="line">    <span class="keyword">public</span> String lastName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Ignore</span></span><br><span class="line">    Bitmap picture;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="注释索引和唯一性"><a href="#注释索引和唯一性" class="headerlink" title="注释索引和唯一性"></a>注释索引和唯一性</h2><hr>
<p>根据您访问数据的方式，您可能需要对数据库中的某些字段进行索引以加快查询速度。 要向实体添加索引，请在@Entity批注中包含indices属性，列出要包含在索引或复合索引中的列的名称。 以下代码片段演示了此注释过程：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span>(indices = &#123;<span class="meta">@Index</span>(<span class="string">"name"</span>),</span><br><span class="line">        <span class="meta">@Index</span>(value = &#123;<span class="string">"last_name"</span>, <span class="string">"address"</span>&#125;)&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    <span class="meta">@PrimaryKey</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> id;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String firstName;</span><br><span class="line">    <span class="keyword">public</span> String address;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"last_name"</span>)</span><br><span class="line">    <span class="keyword">public</span> String lastName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Ignore</span></span><br><span class="line">    Bitmap picture;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>有时，数据库中的某些字段或字段组必须是唯一的。 您可以通过将@Index注释的唯一属性设置为true来强制执行此唯一性属性。 以下代码示例可防止表中有两行包含firstName和lastName列的相同值集合：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span>(indices = &#123;<span class="meta">@Index</span>(value = &#123;<span class="string">"first_name"</span>, <span class="string">"last_name"</span>&#125;,</span><br><span class="line">        unique = <span class="keyword">true</span>)&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    <span class="meta">@PrimaryKey</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"first_name"</span>)</span><br><span class="line">    <span class="keyword">public</span> String firstName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"last_name"</span>)</span><br><span class="line">    <span class="keyword">public</span> String lastName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Ignore</span></span><br><span class="line">    Bitmap picture;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="定义对象之间的关系"><a href="#定义对象之间的关系" class="headerlink" title="定义对象之间的关系"></a>定义对象之间的关系</h2><hr>
<p>由于SQLite是一个关系数据库，您可以指定对象之间的关系。 尽管大多数对象关系映射库允许实体对象相互引用，但Room明确禁止这样做。 要了解此决定背后的技术推理，请参阅了解Room不允许使用对象引用的原因。</p>
<p>即使您不能使用直接关系，Room仍允许您定义实体之间的外键约束。</p>
<p>例如，如果有另一个名为Book的实体，则可以使用@ForeignKey注释来定义它与用户实体的关系，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span>(foreignKeys = <span class="meta">@ForeignKey</span>(entity = User.class,</span><br><span class="line">                                  parentColumns = <span class="string">"id"</span>,</span><br><span class="line">                                  childColumns = <span class="string">"user_id"</span>))</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Book</span> </span>&#123;</span><br><span class="line">    <span class="meta">@PrimaryKey</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> bookId;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String title;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"user_id"</span>)</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> userId;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>外键非常强大，因为它们允许您指定更新引用实体时发生的情况。 例如，如果通过在@ForeignKey注释中包含onDelete = CASCADE删除用户的相应实例，则可以让SQLite删除用户的所有书籍。</p>
<blockquote>
<p><strong>注意：</strong>SQLite将@Insert（onConflict = REPLACE）作为一组REMOVE和REPLACE操作处理，而不是单个UPDATE操作。 这种替换冲突值的方法可能会影响您的外键约束。 有关更多详细信息，请参阅ON_CONFLICT子句的SQLite文档。</p>
</blockquote>
<h2 id="创建嵌套对象"><a href="#创建嵌套对象" class="headerlink" title="创建嵌套对象"></a>创建嵌套对象</h2><hr>
<p>有时，即使对象包含多个字段，您也希望在数据库逻辑中将实体或普通Java对象（POJO）表示为一个有凝聚力的整体。 在这些情况下，您可以使用@Embedded批注来表示您想要分解到表中的子字段的对象。 然后，您可以像查看其他单个列一样查询嵌入字段。</p>
<p>例如，我们的User类可以包含Address类型的字段，它表示名为street，city，state和postCode的字段的组合。 要在表中单独存储组合列，请在用@Embedded注释的User类中包含Address字段，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Address</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> String street;</span><br><span class="line">    <span class="keyword">public</span> String state;</span><br><span class="line">    <span class="keyword">public</span> String city;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name = <span class="string">"post_code"</span>)</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> postCode;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    <span class="meta">@PrimaryKey</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> id;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String firstName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Embedded</span></span><br><span class="line">    <span class="keyword">public</span> Address address;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>表示User对象的表包含具有以下名称的列：id，firstName，street，state，city和post_code。</p>
<blockquote>
<p><strong>注意：</strong>嵌入字段也可以包含其他嵌入字段。</p>
</blockquote>
<p>如果实体具有多个相同类型的嵌入字段，则可以通过设置prefix属性来保持每个列的唯一性。 然后Room会将提供的值添加到嵌入对象中每个列名的开头。</p>
<h1 id="使用Room-DAO访问数据"><a href="#使用Room-DAO访问数据" class="headerlink" title="使用Room DAO访问数据"></a>使用Room DAO访问数据</h1><hr>
<p>要使用Room持久性库访问应用程序的数据，您需要使用数据访问对象或DAO。这套Dao对象构成了Room的主要组件，因为每个DAO都包含提供对应用程序数据库的抽象访问的方法。</p>
<p>通过使用DAO类而不是查询构建器或直接查询访问数据库，可以分离数据库体系结构的不同组件。此外，DAO允许您在测试应用程序时轻松模拟数据库访问。</p>
<p>一个DAO可以是一个接口或一个抽象类。如果它是一个抽象类，它可以有一个构造函数，它将RoomDatabase作为其唯一参数。 Room在编译时创建每个DAO实现。</p>
<blockquote>
<p><strong>注意：</strong>除非已在构建器上调用allowMainThreadQueries()，否则房间不支持主线程上的数据库访问，因为它可能会长时间锁定UI。 异步查询 - 返回LiveData或Flowable实例的查询 - 免于此规则，因为它们在需要时异步地在后台线程上运行查询。</p>
</blockquote>
<h2 id="定义便捷方法"><a href="#定义便捷方法" class="headerlink" title="定义便捷方法"></a>定义便捷方法</h2><hr>
<p>There are multiple convenience queries that you can represent using a DAO class. This document includes several common examples.<br>你可以使用DAO类来表示多种方便的查询，本文档包含几个常见示例。</p>
<h3 id="Insert"><a href="#Insert" class="headerlink" title="Insert"></a>Insert</h3><p>当您创建DAO方法并使用@Insert注释它时，Room会生成一个实现，该实现将所有参数插入到单个事务中的数据库中。</p>
<p>以下代码片段显示了几个示例查询：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Insert</span>(onConflict = OnConflictStrategy.REPLACE)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insertUsers</span><span class="params">(User... users)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Insert</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insertBothUsers</span><span class="params">(User user1, User user2)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Insert</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insertUsersAndFriends</span><span class="params">(User user, List&lt;User&gt; friends)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>如果@Insert方法只接收1个参数，则它可以返回一个long，这是插入项目的新rowId。如果参数是数组或集合，则应该返回<code>long []</code>或<code>List &lt;Long&gt;</code>。</p>
<p>有关更多详细信息，请参阅@Insert注释的参考文档以及rowid表的SQLite文档。</p>
<h3 id="Update"><a href="#Update" class="headerlink" title="Update"></a>Update</h3><p>更新方法根据参数修改数据库内的实体。它使用与每个实体的主键匹配的查询。<br>以下代码片段演示了如何定义此方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Update</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateUsers</span><span class="params">(User... users)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>虽然不是必须的但你还是可以让此方法返回一个int值，以指示数据库中更新的行数。</p>
<h3 id="Delete"><a href="#Delete" class="headerlink" title="Delete"></a>Delete</h3><p>Delete方法根据参数从数据库中删除一组实体。它使用主键来查找要删除的实体。</p>
<p>以下代码片段演示了如何定义此方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Delete</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deleteUsers</span><span class="params">(User... users)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>您可以让此方法返回一个int值，表示从数据库中删除的行数。</p>
<h2 id="查询信息"><a href="#查询信息" class="headerlink" title="查询信息"></a>查询信息</h2><hr>
<p>@Query是DAO类中使用的主要注释。 它允许您对数据库执行读/写操作。 在编译时验证每个@Query方法，因此如果查询出现问题，则会发生编译错误而不是运行时失败。</p>
<p>Room还会验证查询的返回值，以便如果返回对象中的字段名称与查询响应中的相应列名称不匹配，Room会通过以下两种方式之一提醒您：</p>
<ul>
<li>如果只有一些字段名称匹配，它会发出警告。</li>
<li>如果没有字段名称匹配，它会报告错误。</li>
</ul>
<h3 id="简单查询"><a href="#简单查询" class="headerlink" title="简单查询"></a>简单查询</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user"</span>)</span><br><span class="line">    <span class="keyword">public</span> User[] loadAllUsers();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这是一个非常简单的加载所有用户的查询。 在编译时，Room知道它正在查询用户表中的所有列。 如果查询包含语法错误，或者数据库中不存在用户表，则Room将在您的应用程序编译时显示错误并显示相应的消息。</p>
<h3 id="将参数传递给查询"><a href="#将参数传递给查询" class="headerlink" title="将参数传递给查询"></a>将参数传递给查询</h3><p>大多数情况下，您需要将参数传递到查询中以执行过滤操作，例如只显示比特定年龄更早的用户。 要完成此任务，请使用Room注释中的方法参数，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user WHERE age &gt; :minAge"</span>)</span><br><span class="line">    <span class="keyword">public</span> User[] loadAllUsersOlderThan(<span class="keyword">int</span> minAge);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在编译处理此查询时，Room会将minAge绑定参数与minAge方法参数相匹配。房间使用参数名称执行匹配。如果不匹配，则应用程序编译时会发生错误。</p>
<p>你还可以传递多个参数或在查询中多次引用它们，如以下代码片段所示<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge"</span>)</span><br><span class="line">    <span class="keyword">public</span> User[] loadAllUsersBetweenAges(<span class="keyword">int</span> minAge, <span class="keyword">int</span> maxAge);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user WHERE first_name LIKE :search "</span></span><br><span class="line">           + <span class="string">"OR last_name LIKE :search"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;User&gt; <span class="title">findUserWithName</span><span class="params">(String search)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="返回列的子集"><a href="#返回列的子集" class="headerlink" title="返回列的子集"></a>返回列的子集</h3><p>大多数情况下，您只需要获取实体的几个字段。 例如，您的用户界面可能只显示用户的名字和姓氏，而不是每个有关用户的细节。 通过仅提取出现在应用界面中的列，您可以节省宝贵的资源，并且查询更快完成。</p>
<p>Room允许您从查询中返回任何基于Java的对象，只要这些结果列可以映射到返回的对象中即可。 例如，您可以创建以下基于Java的POJO对象来获取用户的名字和姓氏：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NameTuple</span> </span>&#123;</span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name=<span class="string">"first_name"</span>)</span><br><span class="line">    <span class="keyword">public</span> String firstName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ColumnInfo</span>(name=<span class="string">"last_name"</span>)</span><br><span class="line">    <span class="keyword">public</span> String lastName;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>现在你可以在你的查询方法中使用这个POJO：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT first_name, last_name FROM user"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;NameTuple&gt; <span class="title">loadFullName</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>Room理解查询返回first_name和last_name列的值，并且这些值可以映射到NameTuple类的字段中。 因此，Room可以生成正确的代码。 如果查询返回太多的列或NameTuple类中不存在的列，则Room会显示警告。</p>
<blockquote>
<p><strong>注意：</strong>这些POJO也可以使用@Embedded注释。</p>
</blockquote>
<h3 id="传递一组参数"><a href="#传递一组参数" class="headerlink" title="传递一组参数"></a>传递一组参数</h3><p>你的某些查询可能要求您传递可变数量的参数，直到运行时才能知道确切数量的参数。 例如，您可能想要从一部分地区中检索有关所有用户的信息。 Room能够处理当一个参数代表一个集合并且在运行时根据提供的参数数量自动扩展它。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT first_name, last_name FROM user WHERE region IN (:regions)"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;NameTuple&gt; <span class="title">loadUsersFromRegions</span><span class="params">(List&lt;String&gt; regions)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="可观察的查询"><a href="#可观察的查询" class="headerlink" title="可观察的查询"></a>可观察的查询</h3><p>在执行查询时，您经常希望应用的UI在数据更改时自动更新。 要实现这一点，请在查询方法描述中使用类型为LiveData的返回值。 当数据库更新时，会生成所有必要的代码以更新LiveData。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT first_name, last_name FROM user WHERE region IN (:regions)"</span>)</span><br><span class="line">    <span class="keyword">public</span> LiveData&lt;List&lt;User&gt;&gt; loadUsersFromRegionsSync(List&lt;String&gt; regions);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p><strong>注意：</strong>从版本1.0开始，Room使用查询中访问的表的列表来决定是否更新LiveData的实例。</p>
</blockquote>
<h2 id="使用RxJava进行反应查询"><a href="#使用RxJava进行反应查询" class="headerlink" title="使用RxJava进行反应查询"></a>使用RxJava进行反应查询</h2><p>Room还可以从您定义的查询中返回RxJava2 Publisher和Flowable对象。 要使用此功能，请将Room组中的<code>android.arch.persistence.room:rxjava2</code>构件添加到您的构建Gradle依赖项中。 然后，您可以返回RxJava2中定义的类型的对象，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * from user where id = :id LIMIT 1"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Flowable&lt;User&gt; <span class="title">loadUserById</span><span class="params">(<span class="keyword">int</span> id)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>有关更多详细信息，请参阅Google Developers Room和RxJava文章</p>
<h3 id="直接游标访问"><a href="#直接游标访问" class="headerlink" title="直接游标访问"></a>直接游标访问</h3><p>如果您的应用逻辑需要直接访问返回行，则可以从查询中返回Cursor对象，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user WHERE age &gt; :minAge LIMIT 5"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Cursor <span class="title">loadRawUsersOlderThan</span><span class="params">(<span class="keyword">int</span> minAge)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p><strong>警告：</strong>非常不鼓励使用Cursor API，因为它不能保证行是否存在或行包含的值。 只有当您已经拥有需要游标并且无法轻松重构的代码时才使用此功能。</p>
</blockquote>
<h3 id="查询多个表"><a href="#查询多个表" class="headerlink" title="查询多个表"></a>查询多个表</h3><p>有些查询可能需要访问多个表来计算结果。 Room允许你写任何查询，所以你也可以连接表。 此外，如果响应是可观察数据类型（例如Flowable或LiveData），则Room会查看查询中引用的所有表以禁用。</p>
<p>以下代码片段显示了如何执行表连接来合并包含借用图书的用户的表和包含当前借阅图书的数据的表之间的信息：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM book "</span></span><br><span class="line">           + <span class="string">"INNER JOIN loan ON loan.book_id = book.id "</span></span><br><span class="line">           + <span class="string">"INNER JOIN user ON user.id = loan.user_id "</span></span><br><span class="line">           + <span class="string">"WHERE user.name LIKE :userName"</span>)</span><br><span class="line">   <span class="function"><span class="keyword">public</span> List&lt;Book&gt; <span class="title">findBooksBorrowedByNameSync</span><span class="params">(String userName)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>你也可以从这些查询中返回POJO。例如，您可以编写一个查询来加载用户及其宠物的名称，如下所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MyDao</span> </span>&#123;</span><br><span class="line">   <span class="meta">@Query</span>(<span class="string">"SELECT user.name AS userName, pet.name AS petName "</span></span><br><span class="line">          + <span class="string">"FROM user, pet "</span></span><br><span class="line">          + <span class="string">"WHERE user.id = pet.user_id"</span>)</span><br><span class="line">   <span class="keyword">public</span> LiveData&lt;List&lt;UserPet&gt;&gt; loadUserAndPetNames();</span><br><span class="line"></span><br><span class="line">   <span class="comment">// You can also define this class in a separate file, as long as you add the</span></span><br><span class="line">   <span class="comment">// "public" access modifier.</span></span><br><span class="line">   <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">UserPet</span> </span>&#123;</span><br><span class="line">       <span class="keyword">public</span> String userName;</span><br><span class="line">       <span class="keyword">public</span> String petName;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h1 id="迁移Room数据库"><a href="#迁移Room数据库" class="headerlink" title="迁移Room数据库"></a>迁移Room数据库</h1><hr>
<p>在你添加和更改应用中的功能时，您需要修改实体类以反映这些更改。 当用户更新到最新版本的应用程序时，您不希望它们丢失其所有现有数据，特别是当您无法从远程服务器恢复数据时。</p>
<p>Room持久化库允许您编写迁移类来以这种方式保存用户数据。 每个Migration类指定一个startVersion和endVersion。 在运行时，Room会运行每个Migration类的migrate()方法，并使用正确的顺序将数据库迁移到更高版本。</p>
<blockquote>
<p><strong>警告：</strong>如果您不提供必要的迁移，则Room将重建数据库，这意味着您将丢失数据库中的所有数据。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Room.databaseBuilder(getApplicationContext(), MyDb.class, <span class="string">"database-name"</span>)</span><br><span class="line">        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Migration MIGRATION_1_2 = <span class="keyword">new</span> Migration(<span class="number">1</span>, <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">migrate</span><span class="params">(SupportSQLiteDatabase database)</span> </span>&#123;</span><br><span class="line">        database.execSQL(<span class="string">"CREATE TABLE `Fruit` (`id` INTEGER, "</span></span><br><span class="line">                + <span class="string">"`name` TEXT, PRIMARY KEY(`id`))"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Migration MIGRATION_2_3 = <span class="keyword">new</span> Migration(<span class="number">2</span>, <span class="number">3</span>) &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">migrate</span><span class="params">(SupportSQLiteDatabase database)</span> </span>&#123;</span><br><span class="line">        database.execSQL(<span class="string">"ALTER TABLE Book "</span></span><br><span class="line">                + <span class="string">" ADD COLUMN pub_year INTEGER"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>
</blockquote>
<blockquote>
<p><strong>警告：</strong>要保持迁移逻辑按预期运行，请使用完整查询，而不是引用代表查询的常量。</p>
</blockquote>
<p>迁移过程完成后，Room会验证模式以确保迁移正确发生。如果Room找到问题，它会抛出一个包含不匹配信息的异常。</p>
<h2 id="测试迁移"><a href="#测试迁移" class="headerlink" title="测试迁移"></a>测试迁移</h2><p>迁移不是无关紧要的，写入失败可能会导致应用程序崩溃循环。 为了保持您的应用程序的稳定性，您应该事先测试您的迁移。 Room提供了一个测试Maven工件来协助这个测试过程。 但是，要使此工件正常工作，您需要导出数据库的模式。</p>
<h2 id="导出模式"><a href="#导出模式" class="headerlink" title="导出模式"></a>导出模式</h2><p>编译后，Room将数据库的模式信息导出到JSON文件中。要导出模式，请在build.gradle文件中设置room.schemaLocation注释处理器属性，如下面的代码片段所示：</p>
<p><strong>build.gradle</strong><br><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">    ...</span><br><span class="line">    defaultConfig &#123;</span><br><span class="line">        ...</span><br><span class="line">        javaCompileOptions &#123;</span><br><span class="line">            annotationProcessorOptions &#123;</span><br><span class="line">                arguments = [<span class="string">"room.schemaLocation"</span>:</span><br><span class="line">                             <span class="string">"$projectDir/schemas"</span>.toString()]</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>你应该将导出的JSON文件（代表数据库的模式历史记录）存储在版本控制系统中，因为它允许Room为测试目的创建较旧版本的数据库。</p>
<p>要测试这些迁移，请将Room中的<code>android.arch.persistence.room：testing</code> Maven工件添加到测试依赖项中，然后将该模式位置添加为资产文件夹，如以下代码片段所示：</p>
<p><strong>build.gradle</strong><br><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">sourceSets</span> &#123;</span><br><span class="line">        androidTest.assets.srcDirs += files(<span class="string">"$projectDir/schemas"</span>.toString())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>测试包提供了一个MigrationTestHelper类，它可以读取这些模式文件。它还实现JUnit4 TestRule接口，因此它可以管理已创建的数据库。</p>
<p>示例迁移测试出现在以下代码片段中：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(AndroidJUnit4.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MigrationTest</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TEST_DB = <span class="string">"migration-test"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Rule</span></span><br><span class="line">    <span class="keyword">public</span> MigrationTestHelper helper;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MigrationTest</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        helper = <span class="keyword">new</span> MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),</span><br><span class="line">                MigrationDb.class.getCanonicalName(),</span><br><span class="line">                <span class="keyword">new</span> FrameworkSQLiteOpenHelperFactory());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">migrate1To2</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// db has schema version 1. insert some data using SQL queries.</span></span><br><span class="line">        <span class="comment">// You cannot use DAO classes because they expect the latest schema.</span></span><br><span class="line">        db.execSQL(...);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Prepare for the next version.</span></span><br><span class="line">        db.close();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Re-open the database with version 2 and provide</span></span><br><span class="line">        <span class="comment">// MIGRATION_1_2 as the migration process.</span></span><br><span class="line">        db = helper.runMigrationsAndValidate(TEST_DB, <span class="number">2</span>, <span class="keyword">true</span>, MIGRATION_1_2);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// MigrationTestHelper automatically verifies the schema changes,</span></span><br><span class="line">        <span class="comment">// but you need to validate that the data was migrated properly.</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h1 id="测试你的数据库"><a href="#测试你的数据库" class="headerlink" title="测试你的数据库"></a>测试你的数据库</h1><hr>
<p>使用Room持久化库创建数据库时，验证应用数据库和用户数据的稳定性非常重要。</p>
<p>有两种方法可以测试您的数据库:</p>
<ul>
<li>在Android设备上。</li>
<li>在您的主机开发机器上（不推荐）<blockquote>
<p><strong>注意：</strong>在为您的应用运行测试时，Room允许您创建DAO类的模拟实例。 这样，如果您不测试数据库本身，则不需要创建完整的数据库。因此你的DAO不会泄露你的数据库的任何细节。</p>
</blockquote>
<h2 id="在Android设备上测试"><a href="#在Android设备上测试" class="headerlink" title="在Android设备上测试"></a>在Android设备上测试</h2>测试数据库实现的推荐方法是编写在Android设备上运行的JUnit测试。由于这些测试不需要创建活动，它们应该比您的UI测试更快执行。</li>
</ul>
<p>在设置测试时，应该创建数据库的内存中版本，以使测试更加密封，如以下示例所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(AndroidJUnit4.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleEntityReadWriteTest</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> UserDao mUserDao;</span><br><span class="line">    <span class="keyword">private</span> TestDatabase mDb;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">createDb</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Context context = InstrumentationRegistry.getTargetContext();</span><br><span class="line">        mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();</span><br><span class="line">        mUserDao = mDb.getUserDao();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">closeDb</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        mDb.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">writeUserAndReadInList</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        User user = TestUtil.createUser(<span class="number">3</span>);</span><br><span class="line">        user.setName(<span class="string">"george"</span>);</span><br><span class="line">        mUserDao.insert(user);</span><br><span class="line">        List&lt;User&gt; byName = mUserDao.findUsersByName(<span class="string">"george"</span>);</span><br><span class="line">        assertThat(byName.get(<span class="number">0</span>), equalTo(user));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="在你的主机上测试"><a href="#在你的主机上测试" class="headerlink" title="在你的主机上测试"></a>在你的主机上测试</h2><p>Room使用SQLite Support Library，它提供了与Android Framework类中的接口相匹配的接口。此支持允许您传递支持库的自定义实现以测试数据库查询。</p>
<blockquote>
<p><strong>注意：</strong>即使此设置允许您的测试运行速度非常快，也不建议这样做，因为设备上运行的SQLite版本以及您的用户设备可能与主机上的版本不匹配。</p>
</blockquote>
<h1 id="使用Room引用复杂数据"><a href="#使用Room引用复杂数据" class="headerlink" title="使用Room引用复杂数据"></a>使用Room引用复杂数据</h1><hr>
<p>Room提供了原始类型和盒装类型之间转换的功能，但不允许在实体之间进行对象引用。本文档介绍了如何使用类型转换器以及Room不支持对象引用。</p>
<h2 id="使用类型转换器"><a href="#使用类型转换器" class="headerlink" title="使用类型转换器"></a>使用类型转换器</h2><p>你的应用有时需要使用您想要存储在单个数据库列中的自定义数据类型。 要为自定义类型添加这种支持，您需要提供一个TypeConverter，它将自定义类转换为Room可以保留的已知类型。</p>
<p>例如，如果我们想要保存Date的实例，我们可以编写下面的TypeConverter来在数据库中存储等效的Unix时间戳：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Converters</span> </span>&#123;</span><br><span class="line">    <span class="meta">@TypeConverter</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Date <span class="title">fromTimestamp</span><span class="params">(Long value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> value == <span class="keyword">null</span> ? <span class="keyword">null</span> : <span class="keyword">new</span> Date(value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@TypeConverter</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Long <span class="title">dateToTimestamp</span><span class="params">(Date date)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> date == <span class="keyword">null</span> ? <span class="keyword">null</span> : date.getTime();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>前面的例子定义了2个函数，一个将Date对象转换为Long对象，另一个执行反转换，从Long到Date。 由于Room已经知道如何保存Long对象，因此它可以使用此转换器来保存Date类型的值。</p>
<p>接下来，将@TypeConverters注释添加到AppDatabase类，以便Room可以使用您为该AppDatabase中的每个实体和DAO定义的转换器：</p>
<p><strong>AppDatabase.java</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Database</span>(entities = &#123;User.class&#125;, version = <span class="number">1</span>)</span><br><span class="line"><span class="meta">@TypeConverters</span>(&#123;Converters.class&#125;)</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AppDatabase</span> <span class="keyword">extends</span> <span class="title">RoomDatabase</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> UserDao <span class="title">userDao</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>使用这些转换器，您可以在其他查​​询中使用自定义类型，就像使用基本类型一样，如以下代码片段所示：</p>
<p><strong>User.java</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">private</span> Date birthday;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>UserDao.java</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dao</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserDao</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="meta">@Query</span>(<span class="string">"SELECT * FROM user WHERE birthday BETWEEN :from AND :to"</span>)</span><br><span class="line">    <span class="function">List&lt;User&gt; <span class="title">findUsersBornBetweenDates</span><span class="params">(Date from, Date to)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>你还可以将@TypeConverters限制到不同的作用域，包括单个实体，DAO和DAO方法。有关更多详细信息，请参阅@TypeConverters注释的参考文档。</p>
<h2 id="了解Room不允许使用对象引用的原因"><a href="#了解Room不允许使用对象引用的原因" class="headerlink" title="了解Room不允许使用对象引用的原因"></a>了解Room不允许使用对象引用的原因</h2><blockquote>
<p><strong>关键问题：</strong>房间不允许实体类之间的对象引用。相反，您必须明确请求您的应用需要的数据。</p>
</blockquote>
<p>将数据库中的关系映射到相应的对象模型是常见的做法，并且在服务器端运行良好。即使程序在访问时加载字段，服务器仍然运行良好。</p>
<p>但是，在客户端，这种延迟加载不可行，因为它通常发生在UI线程上，并且在UI线程中查询磁盘上的信息会造成严重的性能问题。 UI线程通常具有约16 ms的时间来计算和绘制活动的更新布局，因此即使查询只需要5 ms，仍然可能您的应用程序将耗尽时间来绘制框架，从而导致明显的视觉干扰。 如果有单独的事务并行运行，或者设备正在运行其他磁盘密集型任务，则查询可能需要更多时间才能完成。 但是，如果您不使用延迟加载，则您的应用会获取比需要更多的数据，从而导致内存消耗问题。</p>
<p>对象关系映射通常将这个决定留给开发人员，以便他们可以为他们的应用程序的用例做最好的事情。 开发人员通常决定在应用程序和用户界面之间共享模型。 然而，这种解决方案并不能很好地扩展，因为随着UI的变化，共享模型会产生一些难以让开发人员预测和调试的问题。</p>
<p>例如，考虑加载一个Book对象列表的UI，每个书都有一个Author对象。 您最初可能会将查询设计为使用延迟加载，以便Book的实例使用getAuthor（）方法返回作者。 第一次调用getAuthor（）调用将查询数据库。 过了一段时间，你意识到你也需要在应用程序的用户界面中显示作者姓名。 您可以轻松地添加方法调用，如以下代码片段所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">authorNameTextView.setText(user.getAuthor().getName());</span><br></pre></td></tr></table></figure></p>
<p>但是，这个看起来无害的更改会导致在主线程上查询Author表。</p>
<p>如果提前查询作者信息，如果不再需要数据，则很难更改数据的加载方式。 例如，如果您的应用程序的用户界面不再需要显示作者信息，则您的应用程序会有效地加载不再显示的数据，从而浪费宝贵的内存空间。 如果作者类引用另一个表（如Books），则应用程序的效率会进一步降低。</p>
<p>要使用Room同时引用多个实体，您需要创建一个包含每个实体的POJO，然后编写一个连接相应表的查询。 这种结构良好的模型与Room强大的查询验证功能相结合，可让您的应用在加载数据时消耗更少的资源，从而改善应用的性能和用户体验。</p>

        </div>

        <blockquote class="post-copyright">
    
    <div class="content">
        
<span class="post-time">
    最后更新时间：<time datetime="2018-04-15T04:52:29.497Z" itemprop="dateUpdated">2018-04-15 12:52:29</time>
</span><br>


        
        如有错误，请留言指正。
        
    </div>
    
    <footer>
        <a href="http://yoursite.com">
            <img src="/img/avatar.jpg" alt="马建">
            马建
        </a>
    </footer>
</blockquote>

        
<div class="page-reward">
    <a id="rewardBtn" href="javascript:;" class="page-reward-btn waves-effect waves-circle waves-light">赏</a>
</div>



        <div class="post-footer">
            
	<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Android/">Android</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Architecture-Components/">Architecture Components</a></li></ul>


            
<div class="page-share-wrap">
    

<div class="page-share" id="pageShare">
    <ul class="reset share-icons">
      <li>
        <a class="weibo share-sns" target="_blank" href="http://service.weibo.com/share/share.php?url=http://yoursite.com/2017/10/11/architecture-room/&title=《Android架构组件(七)-Room》 — 随手记&pic=http://yoursite.com/img/avatar.jpg" data-title="微博">
          <i class="icon icon-weibo"></i>
        </a>
      </li>
      <li>
        <a class="weixin share-sns wxFab" href="javascript:;" data-title="微信">
          <i class="icon icon-weixin"></i>
        </a>
      </li>
      <li>
        <a class="qq share-sns" target="_blank" href="http://connect.qq.com/widget/shareqq/index.html?url=http://yoursite.com/2017/10/11/architecture-room/&title=《Android架构组件(七)-Room》 — 随手记&source=desc" data-title=" QQ">
          <i class="icon icon-qq"></i>
        </a>
      </li>
      <li>
        <a class="facebook share-sns" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=http://yoursite.com/2017/10/11/architecture-room/" data-title=" Facebook">
          <i class="icon icon-facebook"></i>
        </a>
      </li>
      <li>
        <a class="twitter share-sns" target="_blank" href="https://twitter.com/intent/tweet?text=《Android架构组件(七)-Room》 — 随手记&url=http://yoursite.com/2017/10/11/architecture-room/&via=http://yoursite.com" data-title=" Twitter">
          <i class="icon icon-twitter"></i>
        </a>
      </li>
      <li>
        <a class="google share-sns" target="_blank" href="https://plus.google.com/share?url=http://yoursite.com/2017/10/11/architecture-room/" data-title=" Google+">
          <i class="icon icon-google-plus"></i>
        </a>
      </li>
    </ul>
 </div>



    <a href="javascript:;" id="shareFab" class="page-share-fab waves-effect waves-circle">
        <i class="icon icon-share-alt icon-lg"></i>
    </a>
</div>



        </div>
    </div>

    
<nav class="post-nav flex-row flex-justify-between">
  
    <div class="waves-block waves-effect prev">
      <a href="/2017/10/12/architecture-paging/" id="post-prev" class="post-nav-link">
        <div class="tips"><i class="icon icon-angle-left icon-lg icon-pr"></i> Prev</div>
        <h4 class="title">Android架构组件(8)-Paging</h4>
      </a>
    </div>
  

  
    <div class="waves-block waves-effect next">
      <a href="/2017/09/28/architecture-saving-states/" id="post-next" class="post-nav-link">
        <div class="tips">Next <i class="icon icon-angle-right icon-lg icon-pl"></i></div>
        <h4 class="title">Android架构组件(六)-保存状态</h4>
      </a>
    </div>
  
</nav>



    














</article>

<div id="reward" class="page-modal reward-lay">
    <a class="close" href="javascript:;"><i class="icon icon-close"></i></a>
    <h3 class="reward-title">
        <i class="icon icon-quote-left"></i>
        谢谢大爷~
        <i class="icon icon-quote-right"></i>
    </h3>
    <div class="reward-content">
        
        <div class="reward-code">
            <img id="rewardCode" src="/img/wechat.jpg" alt="打赏二维码">
        </div>
        
        <label class="reward-toggle">
            <input id="rewardToggle" type="checkbox" class="reward-toggle-check"
                data-wechat="/img/wechat.jpg" data-alipay="/img/alipay.jpg">
            <div class="reward-toggle-ctrol">
                <span class="reward-toggle-item wechat">微信</span>
                <span class="reward-toggle-label"></span>
                <span class="reward-toggle-item alipay">支付宝</span>
            </div>
        </label>
        
    </div>
</div>



</div>

        <footer class="footer">
    <div class="top">
        
<p>
    <span id="busuanzi_container_site_uv" style='display:none'>
        站点总访客数：<span id="busuanzi_value_site_uv"></span>
    </span>
    <span id="busuanzi_container_site_pv" style='display:none'>
        站点总访问量：<span id="busuanzi_value_site_pv"></span>
    </span>
</p>


        <p>
            
                <span><a href="/atom.xml" target="_blank" class="rss" title="rss"><i class="icon icon-lg icon-rss"></i></a></span>
            
            <span>博客内容遵循 <a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">知识共享 署名 - 非商业性 - 相同方式共享 4.0 国际协议</a></span>
        </p>
    </div>
    <div class="bottom">
        <p><span>马建 &copy; 2015 - 2019</span>
            <span>
                
            </span>
        </p>
    </div>
</footer>

    </main>
    <div class="mask" id="mask"></div>
<a href="javascript:;" id="gotop" class="waves-effect waves-circle waves-light"><span class="icon icon-lg icon-chevron-up"></span></a>



<div class="global-share" id="globalShare">
    <ul class="reset share-icons">
      <li>
        <a class="weibo share-sns" target="_blank" href="http://service.weibo.com/share/share.php?url=http://yoursite.com/2017/10/11/architecture-room/&title=《Android架构组件(七)-Room》 — 随手记&pic=http://yoursite.com/img/avatar.jpg" data-title="微博">
          <i class="icon icon-weibo"></i>
        </a>
      </li>
      <li>
        <a class="weixin share-sns wxFab" href="javascript:;" data-title="微信">
          <i class="icon icon-weixin"></i>
        </a>
      </li>
      <li>
        <a class="qq share-sns" target="_blank" href="http://connect.qq.com/widget/shareqq/index.html?url=http://yoursite.com/2017/10/11/architecture-room/&title=《Android架构组件(七)-Room》 — 随手记&source=desc" data-title=" QQ">
          <i class="icon icon-qq"></i>
        </a>
      </li>
      <li>
        <a class="facebook share-sns" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=http://yoursite.com/2017/10/11/architecture-room/" data-title=" Facebook">
          <i class="icon icon-facebook"></i>
        </a>
      </li>
      <li>
        <a class="twitter share-sns" target="_blank" href="https://twitter.com/intent/tweet?text=《Android架构组件(七)-Room》 — 随手记&url=http://yoursite.com/2017/10/11/architecture-room/&via=http://yoursite.com" data-title=" Twitter">
          <i class="icon icon-twitter"></i>
        </a>
      </li>
      <li>
        <a class="google share-sns" target="_blank" href="https://plus.google.com/share?url=http://yoursite.com/2017/10/11/architecture-room/" data-title=" Google+">
          <i class="icon icon-google-plus"></i>
        </a>
      </li>
    </ul>
 </div>


<div class="page-modal wx-share" id="wxShare">
    <a class="close" href="javascript:;"><i class="icon icon-close"></i></a>
    <p>扫一扫，分享到微信</p>
    <img src="" alt="微信分享二维码">
</div>




    <script src="//cdn.bootcss.com/node-waves/0.7.4/waves.min.js"></script>
<script>
var BLOG = { ROOT: '/', SHARE: true, REWARD: true };


</script>

<script src="/js/main.min.js?v=1.7.2"></script>


<div class="search-panel" id="search-panel">
    <ul class="search-result" id="search-result"></ul>
</div>
<template id="search-tpl">
<li class="item">
    <a href="{path}" class="waves-block waves-effect">
        <div class="title ellipsis" title="{title}">{title}</div>
        <div class="flex-row flex-middle">
            <div class="tags ellipsis">
                {tags}
            </div>
            <time class="flex-col time">{date}</time>
        </div>
    </a>
</li>
</template>

<script src="/js/search.min.js?v=1.7.2" async></script>






<script async src="//dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>



<script>
(function() {
    var OriginTitile = document.title, titleTime;
    document.addEventListener('visibilitychange', function() {
        if (document.hidden) {
            document.title = '随手记';
            clearTimeout(titleTime);
        } else {
            document.title = '随手记';
            titleTime = setTimeout(function() {
                document.title = OriginTitile;
            },2000);
        }
    });
})();
</script>



</body>
</html>
